import tkinter as tk
from tkinter import filedialog
from tkinter import messagebox
from PIL import Image, ImageTk, ImageEnhance
import os
import numpy as np
import sys
import subprocess
import cv2

# 模拟缺失的模块功能
class ImageIO:
    @staticmethod
    def load_image(path, as_gray=False):
        if as_gray:
            img = cv2.imread(path, cv2.IMREAD_GRAYSCALE)
        else:
            img = cv2.imread(path, cv2.IMREAD_COLOR)
            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        return img

    @staticmethod
    def save_image(image, path):
        if len(image.shape) == 3 and image.shape[2] == 3:
            image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
        cv2.imwrite(path, image)

class ImageChannels:
    @staticmethod
    def split_image_channels(image):
        if len(image.shape) == 2:
            return [image]
        return [image[:,:,i] for i in range(image.shape[2])]

    @staticmethod
    def merge_image_channels(paths):
        channels = []
        for path in paths:
            img = ImageIO.load_image(path, as_gray=True)
            channels.append(img)
        return np.stack(channels, axis=2)

# 创建模拟模块实例
image_io = ImageIO()
image_channels = ImageChannels()
image_transform = sys.modules.get('image_transform', None)
config = type('Config', (), {'DEFAULT_SAVE_FORMAT': '.png'})()
from image_utils import language_data, logging
import image_utils as utils


class ImageProcessor:
    def __init__(self, master):
        """初始化ImageProcessor类的实例"""
        self.master = master  # 主窗口
        self.current_language = language_data["current_language"]  # 默认语言通过JSON文件直接设置
        master.title(language_data[self.current_language]['app_title'])  # 设置初始窗口标题

        self.image = None  # 用于存储当前图像的numpy数组
        self.image_path = ""  # 用于存储当前图像的文件路径
        self.rect_start_x = None  # 用于存储裁剪矩形框起始点的x坐标
        self.rect_start_y = None  # 用于存储裁剪矩形框起始点的y坐标
        self.rect_end_x = None  # 用于存储裁剪矩形框结束点的x坐标
        self.rect_end_y = None  # 用于存储裁剪矩形框结束点的y坐标
        self.is_drawing = False  # 用于标记是否正在绘制裁剪矩形框
        self.current_rect = None  # 用于存储当前绘制的裁剪矩形框对象

        self.create_widgets()  # 创建GUI组件

        # 初始化亮度因子
        self.brightness_factor = 1.0
        self.original_image = None  # 用于保存原始图像数据,便于亮度恢复
        self.thresh_preview_window = None
        self.thresh_slider = None
        self.thresh_apply_btn = None
        self.thresh_preview_canvas = None
        self.thresh_current = 128
        self.edge_preview_window = None
        self.edge_slider = None
        self.edge_preview_canvas = None
        self.edge_shift = 1
        self.mask_image = None  # 当前生成的掩模
        self.mask_window = None
        self.mask_preview_canvas = None
        self.mask_color = 'red'
        self.mask_apply_window = None
        self.mask_result_image = None

    def create_widgets(self):
        """创建GUI组件"""
        # 创建菜单栏
        menubar = tk.Menu(self.master)
        self.master.config(menu=menubar)

        # 创建文件菜单
        file_menu = tk.Menu(menubar)
        file_menu.add_command(label=language_data[self.current_language]['open_original'], command=self.Open_as_original)
        file_menu.add_command(label=language_data[self.current_language]['open_grayscale'], command=self.Open_as_gray)
        file_menu.add_command(label=language_data[self.current_language]['merge_channels'], command=self.merge_channels)
        file_menu.add_command(label=language_data[self.current_language]['save_image'], command=self.save_image)
        file_menu.add_separator()
        
        # 创建一个菜单项,用于切换语言,它应该有两个子菜单项,分别用于切换中文和英文。当前语言用一个勾选标记来表示。
        language_menu = tk.Menu(file_menu)
        self.language_var = tk.StringVar(value=self.current_language)
        language_menu.add_radiobutton(label="中文", variable=self.language_var, value="zh", command=self.change_language)
        language_menu.add_radiobutton(label="English", variable=self.language_var, value="en", command=self.change_language)
        file_menu.add_cascade(label=language_data[self.current_language]['language'], menu=language_menu)
        
        file_menu.add_command(label=language_data[self.current_language]['exit'], command=self.master.quit)
        menubar.add_cascade(label=language_data[self.current_language]['file'], menu=file_menu)

        # 创建信息菜单
        info_menu = tk.Menu(menubar)
        info_menu.add_command(label=language_data[self.current_language]['details'], command=self.show_image_details)
        menubar.add_cascade(label=language_data[self.current_language]['info'], menu=info_menu)

        # 创建编辑菜单
        edit_menu = tk.Menu(menubar)
        edit_menu.add_command(label=language_data[self.current_language]['crop'], command=self.crop_image)
        edit_menu.add_command(label=language_data[self.current_language]['reset'], command=self.reset_image)
        edit_menu.add_command(label="亮度调节", command=self.show_brightness_dialog)
        menubar.add_cascade(label=language_data[self.current_language]['edit'], menu=edit_menu)

        # 创建阈值处理菜单
        threshold_menu = tk.Menu(menubar)
        threshold_menu.add_command(label="全局阈值处理", command=self.show_threshold_dialog)
        menubar.add_cascade(label="阈值处理", menu=threshold_menu)

        # 创建边缘检测菜单
        edge_menu = tk.Menu(menubar)
        edge_menu.add_command(label="移动差分", command=self.show_edge_diff_dialog)
        menubar.add_cascade(label="边缘", menu=edge_menu)

        # 创建图像分割菜单
        split_menu = tk.Menu(menubar)
        menubar.add_cascade(label=language_data[self.current_language]['split'], menu=split_menu)
        split_menu.add_command(label=language_data[self.current_language]['split_channels'], command=self.split_image_channels)

        # 创建掩模菜单
        mask_menu = tk.Menu(menubar)
        mask_menu.add_command(label="生成红色掩模", command=lambda: self.show_mask_dialog('red'))
        mask_menu.add_command(label="生成绿色掩模", command=lambda: self.show_mask_dialog('green'))
        mask_menu.add_command(label="生成蓝色掩模", command=lambda: self.show_mask_dialog('blue'))
        menubar.add_cascade(label="掩模", menu=mask_menu)

        # 创建反色与非运算菜单
        invert_menu = tk.Menu(menubar)
        invert_menu.add_command(label="反色功能", command=self.invert_image)
        menubar.add_cascade(label="反色/非运算", menu=invert_menu)

        # 创建图像显示区域
        self.canvas = tk.Canvas(self.master, width=600, height=400)
        self.canvas.pack()

        self.canvas.bind("<Button-1>", self.on_mouse_down)
        self.canvas.bind("<B1-Motion>", self.on_mouse_move)
        self.canvas.bind("<ButtonRelease-1>", self.on_mouse_up)
    
    def change_language(self):
        """切换语言"""
        # 把language.json文件中的current_language的值改为当前的self.current_language
        self.current_language = self.language_var.get()  # 获取所选择的语言
        utils.write_language_data(language_data, "current_language", self.current_language)
        logging.info(f"当前语言已切换为{self.current_language}。")
        confirm = messagebox.askyesno(language_data[self.current_language]['confirm_restart'],
                                  language_data[self.current_language]['restart_warning'])
        if confirm:
            self.master.destroy()
            # os.execl(sys.executable, sys.executable, *sys.argv)
            subprocess.Popen([sys.executable] + sys.argv)

    def merge_channels(self):
        """合并多个单通道图像为一个多通道图像"""
        # 打开文件对话框,让用户选择多个图像文件
        file_paths = filedialog.askopenfilenames()
        # 如果用户没有选择文件,直接返回
        if not file_paths:
            return
        
        # 如果只选择了一个文件,显示错误信息并返回
        if len(file_paths) == 1:
            tk.messagebox.showerror(language_data[self.current_language]['error'], language_data[self.current_language]['single_file_error'])
            return
        
        try:
            self.image = image_channels.merge_image_channels(file_paths)
        except ValueError as e:
            tk.messagebox.showerror(language_data[self.current_language]['error'], str(e))
            return
        
        # 显示当前图像
        self.display_image()

    def split_image_channels(self):
        """分割当前图像的通道,并在新窗口中显示每个通道"""
        if self.image is None:
            tk.messagebox.showinfo(language_data[self.current_language]['error'], language_data[self.current_language]['no_image_loaded'])
            return

        if len(self.image.shape) == 2:
            tk.messagebox.showinfo(language_data[self.current_language]['error'], language_data[self.current_language]['single_channel_error'])
            return

        try:
            channels = image_channels.split_image_channels(self.image)
        except ValueError as e:
            tk.messagebox.showinfo(language_data[self.current_language]['error'], str(e))
            return
        
        for i, channel in enumerate(channels):
            window = tk.Toplevel(self.master)
            window.title(language_data[self.current_language]['channel_title'].format(i))
            canvas = tk.Canvas(window, width=300, height=300)
            canvas.pack()

            # 定义内部函数用于保存单个通道
            def save_channel(channel, index):
                save_path = filedialog.asksaveasfilename(defaultextension=config.DEFAULT_SAVE_FORMAT)
                if not save_path:
                    return
                image_io.save_image(channel, save_path)
                tk.messagebox.showinfo(language_data[self.current_language]['success'], language_data[self.current_language]['channel_saved'].format(index, save_path))
            
            save_button = tk.Button(window, text=language_data[self.current_language]['save_channel'], command=lambda channel=channel: save_channel(channel, i))
            save_button.pack()

            window.update()

            # 显示子通道图像
            image = Image.fromarray(channel)  # 将通道数据转换为PIL图像对象
            canvas_width = canvas.winfo_width()  # 获取画布的宽度
            canvas_height = canvas.winfo_height()  # 获取画布的高度
            image_width, image_height = image.size  # 获取图像的宽度和高度
            scale = min(canvas_width / image_width, canvas_height / image_height)  # 计算图像的缩放比例
            if scale < 1:  # 如果图像过大,缩小图像
                new_width = int(image_width * scale)
                new_height = int(image_height * scale)
                image = image.resize((new_width, new_height), Image.LANCZOS)  # 使用Lanczos滤波器进行缩小
            else:  # 如果图像过小,放大图像
                new_width = int(image_width * scale)
                new_height = int(image_height * scale)
                image = image.resize((new_width, new_height), Image.BICUBIC)  # 使用双三次滤波器进行放大

            photo = ImageTk.PhotoImage(image)  # 将PIL图像对象转换为PhotoImage对象
            x = (canvas_width - new_width) // 2  # 计算图像在画布上的水平居中位置
            y = (canvas_height - new_height) // 2  # 计算图像在画布上的垂直居中位置
            canvas.create_image(x, y, anchor=tk.NW, image=photo)  # 在画布上显示图像
            canvas.image = photo  # 将PhotoImage对象保存到画布中,防止被垃圾回收

            # 在子图上添加清晰的标题
            title = f"Channel {i}"
            canvas.create_text(canvas_width//2, 15, text=title, font=("Arial", 15), fill="black")

        logging.info("图像分割操作成功。")  # 记录成功日志

    def show_image_details(self):
        """显示当前图像的详细信息"""
        if self.image is None:  # 如果当前没有图像,显示相应消息并返回
            messagebox.showinfo(language_data[self.current_language]['error'], language_data[self.current_language]['no_image_loaded'])
            logging.info("没有载入任何图像,无法显示图像细节。")  # 记录日志信息
            return

        # 从路径中提取文件名
        filename = os.path.basename(self.image_path)

        # 计算其他基本信息
        total_pixels = self.image.size  # 获取图像的总像素数
        shape = self.image.shape  # 获取图像的形状(高度,宽度,通道数)
        channels = 1 if len(self.image.shape) == 2 else self.image.shape[2]  # 获取图像的通道数
        dtype = self.image.dtype  # 获取图像像素的数据类型

        # 构建信息字符串,包括文件名
        details = language_data[self.current_language]['info_str'].format(filename,total_pixels,shape,channels,dtype)

        # 显示信息
        messagebox.showinfo("INFO", details)
        logging.info("已显示图像细节。")  # 记录成功日志

    def on_mouse_down(self, event):
        """处理鼠标左键按下事件"""
        if self.current_rect and not self.is_drawing:
            # 如果存在矩形框且不是在拖动状态,删除矩形框并重置状态
            self.canvas.delete(self.current_rect)
            self.current_rect = None
        else:
            # 否则,开始绘制新的矩形框
            self.rect_start_x = event.x
            self.rect_start_y = event.y
            self.is_drawing = True

    def on_mouse_move(self, event):
        """处理鼠标左键移动事件"""
        if self.is_drawing:
            if self.current_rect:
                self.canvas.delete(self.current_rect)
            self.current_rect = self.canvas.create_rectangle(self.rect_start_x, self.rect_start_y, event.x, event.y, outline="red")

    def on_mouse_up(self, event):
        """处理鼠标左键释放事件"""
        # 标记矩形的结束点并完成绘制
        self.rect_end_x, self.rect_end_y = event.x, event.y
        self.is_drawing = False
        # 检查坐标有效性，防止NoneType报错
        if None in [self.rect_start_x, self.rect_start_y, self.rect_end_x, self.rect_end_y]:
            tk.messagebox.showinfo("错误", "裁剪坐标无效，请重新操作！")
            logging.error("裁剪坐标有None，操作被终止。")
            return
        # 保存裁剪区域的坐标
        self.crop_region = (min(self.rect_start_y, self.rect_end_y), max(self.rect_start_y, self.rect_end_y),
                            min(self.rect_start_x, self.rect_end_x), max(self.rect_start_x, self.rect_end_x))
        logging.info("已绘制裁剪区域。")  # 记录成功日志

    def Open_as_original(self):
        """打开原始图像"""
        logging.info("正在打开原始图像...") 
        self.image_path = filedialog.askopenfilename()  # 打开文件对话框,让用户选择图像文件
        if not self.image_path:
            logging.info("没有选择任何图像文件,打开操作已取消。")  # 记录日志信息
            return  # 如果没有选择任何文件,直接返回
        self.image = image_io.load_image(self.image_path)  # 加载图像文件
        self.original_image = self.image.copy()  # 每次打开新图像时保存原始图像
        self.brightness_factor = 1.0  # 重置亮度因子
        self.display_image()  # 显示图像
        logging.info("成功打开原始图像。")  # 记录成功日志
    
    def Open_as_gray(self):
        """将原始图像转换为灰度图像"""
        self.image_path = filedialog.askopenfilename()  # 打开文件对话框,让用户选择图像文件
        if not self.image_path:
            logging.info("没有选择任何图像文件,打开操作已取消。")  # 记录日志信息
            return  # 如果没有选择任何文件,直接返回
        self.image = image_io.load_image(self.image_path, as_gray=True)  # 加载图像文件并转换为灰度图像
        self.original_image = self.image.copy()  # 保存原始图像
        self.brightness_factor = 1.0
        self.display_image()  # 显示图像
        logging.info("成功打开并转换为灰度图像。")  # 记录成功日志

    def display_image(self):
        """在主画布上显示当前图像"""
        if self.image is None:
            logging.info("当前没有图像可显示。")  # 记录日志信息
            return  # 如果当前没有图像,直接返回

        image = Image.fromarray(self.image)  # 将numpy数组转换为PIL图像对象

        # 计算图像的缩放比例
        canvas_width = self.canvas.winfo_width()  # 获取画布的宽度
        canvas_height = self.canvas.winfo_height()  # 获取画布的高度
        image_width, image_height = image.size  # 获取图像的宽度和高度
        scale = min(canvas_width / image_width, canvas_height / image_height)  # 计算图像的缩放比例

        # 缩放图像
        if scale < 1:
            # 图像过大,缩小图像
            new_width = int(image_width * scale)
            new_height = int(image_height * scale)
            image = image.resize((new_width, new_height), Image.LANCZOS)  # 使用Lanczos滤波器进行缩小
        else:
            # 图像过小,放大图像
            new_width = int(image_width * scale)
            new_height = int(image_height * scale)
            image = image.resize((new_width, new_height), Image.BICUBIC)  # 使用双三次滤波器进行放大

        photo = ImageTk.PhotoImage(image)  # 将PIL图像对象转换为PhotoImage对象
        self.canvas.delete("all")  # 清除画布上的所有内容

        # 计算图像在画布上的居中位置
        x = (canvas_width - new_width) // 2
        y = (canvas_height - new_height) // 2
        self.canvas.create_image(x, y, anchor=tk.NW, image=photo)  # 在画布上显示图像, tk.NW表示图像的左上角对齐画布的左上角
        self.canvas.image = photo  # 将PhotoImage对象保存到画布中,防止被垃圾回收
        logging.info("成功显示图像。")  # 记录成功日志

    def save_image(self):
        """保存当前图像"""
        if self.image is None:
            tk.messagebox.showinfo(language_data[self.current_language]['error'], language_data[self.current_language]['no_image_to_be_saved'])
            logging.error("没有图像可保存,保存操作已取消。")  # 记录错误日志
            return  # 如果当前没有图像,直接返回

        save_path = filedialog.asksaveasfilename(defaultextension=config.DEFAULT_SAVE_FORMAT)  # 打开保存文件对话框
        if not save_path:
            logging.info("没有选择保存路径,保存操作已取消。")  # 记录日志信息
            return  # 如果没有选择保存路径,直接返回

        image_io.save_image(self.image, save_path)  # 将当前图像保存到指定路径
        logging.info(f"图像已成功保存到 {save_path}。")  # 记录成功日志

    def crop_image(self):
        """裁剪当前图像"""
        if self.image is None:
            tk.messagebox.showinfo(language_data[self.current_language]['error'], language_data[self.current_language]['no_image_to_be_cropped'])
            logging.error("没有图像可裁剪,裁剪操作已取消。")  # 记录错误日志
            return  # 如果当前没有图像,直接返回

        # 检查是否有绘制的裁剪区域
        if hasattr(self, 'crop_region') and self.current_rect:
            # 计算实际裁剪区域坐标(考虑图像缩放和偏移)
            image_height, image_width = self.image.shape[:2]  # 获取图像的高度和宽度
            canvas_width = self.canvas.winfo_width()  # 获取画布的宽度
            canvas_height = self.canvas.winfo_height()  # 获取画布的高度
            scale = min(canvas_width / image_width, canvas_height / image_height)  # 计算图像的缩放比例
            offset_x = (canvas_width - image_width * scale) // 2  # 计算图像在画布上的水平偏移量
            offset_y = (canvas_height - image_height * scale) // 2  # 计算图像在画布上的垂直偏移量

            # 计算实际裁剪区域坐标
            real_crop_region = (
                int((self.crop_region[0] - offset_y) / scale),
                int((self.crop_region[1] - offset_y) / scale),
                int((self.crop_region[2] - offset_x) / scale),
                int((self.crop_region[3] - offset_x) / scale)
            )

            cropped_image = image_transform.crop_image(self.image, real_crop_region)  # 裁剪图像
            if cropped_image is not None:
                self.image = cropped_image  # 将裁剪后的图像保存到self.image中
                self.display_image()  # 显示裁剪后的图像
                logging.info("图像裁剪成功。")  # 记录成功日志

                # 清零裁剪框信息
                self.canvas.delete(self.current_rect)  # 删除画布上的裁剪矩形框
                self.current_rect = None  # 将当前裁剪矩形框对象设置为None
                del self.crop_region  # 删除存储裁剪区域坐标的属性
            else:
                tk.messagebox.showinfo(language_data[self.current_language]['error'], language_data[self.current_language]['crop_failed'])
                logging.error("裁剪操作失败。")  # 记录错误日志
        else:
            tk.messagebox.showinfo(language_data[self.current_language]['error'], language_data[self.current_language]['no_crop_area'])
            logging.error("没有选择裁剪区域,裁剪操作已取消。")  # 记录错误日志

    def reset_image(self):
        """重置当前图像为原始状态"""
        if not self.image_path:  # 如果没有图像路径,显示错误消息并返回
            tk.messagebox.showinfo(language_data[self.current_language]['error'], language_data[self.current_language]['no_reset_image'])
            logging.error("没有图像可重置,重置操作已取消。")  # 记录错误日志
            return
        self.image = image_io.load_image(self.image_path)  # 重新加载图像
        self.original_image = self.image.copy()  # 重置原始图像
        self.brightness_factor = 1.0
        self.display_image()  # 显示图像
        logging.info("图像已重置为原始状态。")  # 记录成功日志

    def show_brightness_dialog(self):
        """弹出亮度调节对话框"""
        if self.image is None:
            tk.messagebox.showinfo("提示", "没有载入任何图像。")
            return
        # 保存原始图像
        if self.original_image is None:
            self.original_image = self.image.copy()
        dialog = tk.Toplevel(self.master)
        dialog.title("亮度调节")
        tk.Label(dialog, text="亮度(0%~200%)").pack(pady=10)
        scale = tk.Scale(dialog, from_=0, to=200, orient=tk.HORIZONTAL, length=300, resolution=1)
        scale.set(int(self.brightness_factor * 100))
        scale.pack(padx=20, pady=10)
        def on_scale(val):
            factor = float(val) / 100.0
            self.adjust_brightness(factor)
        scale.config(command=on_scale)
        def on_close():
            dialog.destroy()
        btn = tk.Button(dialog, text="关闭", command=on_close)
        btn.pack(pady=5)

    def adjust_brightness(self, factor):
        """调整图像亮度"""
        if self.original_image is None:
            self.original_image = self.image.copy()
        img = Image.fromarray(self.original_image)
        enhancer = ImageEnhance.Brightness(img)
        bright_img = enhancer.enhance(factor)
        self.brightness_factor = factor
        self.image = np.array(bright_img).astype(self.original_image.dtype)
        self.display_image()

    def show_threshold_dialog(self):
        """弹出全局阈值处理对话框，只能对灰度图使用"""
        if self.image is None or len(self.image.shape) != 2:
            tk.messagebox.showinfo("提示", "请先载入灰度图像（单通道）！")
            return
        if self.thresh_preview_window is not None:
            self.thresh_preview_window.destroy()
        self.thresh_preview_window = tk.Toplevel(self.master)
        self.thresh_preview_window.title("全局阈值处理")
        tk.Label(self.thresh_preview_window, text="全局阈值(0~255)").pack(pady=10)
        self.thresh_slider = tk.Scale(self.thresh_preview_window, from_=0, to=255, orient=tk.HORIZONTAL, length=300, resolution=1)
        self.thresh_slider.set(self.thresh_current)
        self.thresh_slider.pack(padx=20, pady=10)
        self.thresh_preview_canvas = tk.Canvas(self.thresh_preview_window, width=300, height=200)
        self.thresh_preview_canvas.pack(pady=10)
        self.update_threshold_preview(self.thresh_current)
        def on_thresh_change(val):
            t = int(val)
            self.thresh_current = t
            self.update_threshold_preview(t)
        self.thresh_slider.config(command=on_thresh_change)
        self.thresh_apply_btn = tk.Button(self.thresh_preview_window, text="应用", command=self.apply_threshold)
        self.thresh_apply_btn.pack(pady=5)
        def on_close():
            self.thresh_preview_window.destroy()
            self.thresh_preview_window = None
        self.thresh_preview_window.protocol("WM_DELETE_WINDOW", on_close)

    def update_threshold_preview(self, thresh):
        """更新阈值处理预览"""
        if self.image is None or len(self.image.shape) != 2:
            return
        img = self.image.copy()
        preview = np.where(img > thresh, 255, 0).astype(np.uint8)
        pil_img = Image.fromarray(preview)
        pil_img = pil_img.resize((300, 200))
        photo = ImageTk.PhotoImage(pil_img)
        self.thresh_preview_canvas.delete("all")
        self.thresh_preview_canvas.create_image(0, 0, anchor=tk.NW, image=photo)
        self.thresh_preview_canvas.image = photo

    def apply_threshold(self):
        """应用当前阈值到原图(只对灰度图有效)"""
        if self.image is None or len(self.image.shape) != 2:
            tk.messagebox.showinfo("提示", "请先载入灰度图像（单通道）！")
            return
        thresh = self.thresh_current
        img = self.image.copy()
        result = np.where(img > thresh, 255, 0).astype(np.uint8)
        self.image = result
        self.original_image = self.image.copy()
        self.display_image()
        tk.messagebox.showinfo("提示", f"已应用全局阈值处理，阈值={thresh}")
        if self.thresh_preview_window is not None:
            self.thresh_preview_window.destroy()
            self.thresh_preview_window = None

    def show_edge_diff_dialog(self):
        """弹出移动差分边缘检测对话框，只能对灰度图使用"""
        if self.image is None or len(self.image.shape) != 2:
            tk.messagebox.showinfo("提示", "请先载入灰度图像（单通道）！")
            return
        if self.edge_preview_window is not None:
            self.edge_preview_window.destroy()
        self.edge_preview_window = tk.Toplevel(self.master)
        self.edge_preview_window.title("移动差分边缘检测")
        tk.Label(self.edge_preview_window, text="像素移动(-100~100)").pack(pady=10)
        self.edge_slider = tk.Scale(self.edge_preview_window, from_=-100, to=100, orient=tk.HORIZONTAL, length=300, resolution=1)
        self.edge_slider.set(self.edge_shift)
        self.edge_slider.pack(padx=20, pady=10)
        self.edge_preview_canvas = tk.Canvas(self.edge_preview_window, width=300, height=200)
        self.edge_preview_canvas.pack(pady=10)
        self.update_edge_diff_preview(self.edge_shift)
        def on_edge_change(val):
            shift = int(val)
            self.edge_shift = shift
            self.update_edge_diff_preview(shift)
        self.edge_slider.config(command=on_edge_change)
        def on_close():
            self.edge_preview_window.destroy()
            self.edge_preview_window = None
        self.edge_preview_window.protocol("WM_DELETE_WINDOW", on_close)

    def update_edge_diff_preview(self, shift):
        """更新移动差分边缘图预览"""
        if self.image is None or len(self.image.shape) != 2 or shift == 0:
            # 若未加载灰度图或移动为0，显示全黑
            preview = np.zeros_like(self.image)
        else:
            img = self.image.copy()
            if shift < 0:
                shifted = np.roll(img, shift, axis=1)
                shifted[:, shift:] = 0  # 左移补0
            else:
                shifted = np.roll(img, shift, axis=1)
                if shift > 0:
                    shifted[:, :shift] = 0  # 右移补0
            preview = np.abs(img.astype(np.int16) - shifted.astype(np.int16)).astype(np.uint8)
        pil_img = Image.fromarray(preview)
        pil_img = pil_img.resize((300, 200))
        photo = ImageTk.PhotoImage(pil_img)
        self.edge_preview_canvas.delete("all")
        self.edge_preview_canvas.create_image(0, 0, anchor=tk.NW, image=photo)
        self.edge_preview_canvas.image = photo

    def apply_edge_diff(self):
        """应用当前移动差分到主图像(只对灰度图有效)"""
        if self.image is None or len(self.image.shape) != 2 or self.edge_shift == 0:
            tk.messagebox.showinfo("提示", "请先载入灰度图像（单通道）并选择非零移动！")
            return
        img = self.image.copy()
        shift = self.edge_shift
        if shift < 0:
            shifted = np.roll(img, shift, axis=1)
            shifted[:, shift:] = 0
        else:
            shifted = np.roll(img, shift, axis=1)
            if shift > 0:
                shifted[:, :shift] = 0
        result = np.abs(img.astype(np.int16) - shifted.astype(np.int16)).astype(np.uint8)
        self.image = result
        self.original_image = self.image.copy()
        self.display_image()
        tk.messagebox.showinfo("提示", f"已应用移动差分，像素移动={shift}")
        if self.edge_preview_window is not None:
            self.edge_preview_window.destroy()
            self.edge_preview_window = None

    def show_mask_dialog(self, color):
        """弹出掩模生成窗口，支持红、绿、蓝三色掩模"""
        if self.image is None:
            tk.messagebox.showinfo("提示", "请先载入一张图像！")
            return
        if self.mask_window is not None:
            self.mask_window.destroy()
        self.mask_color = color
        self.mask_window = tk.Toplevel(self.master)
        self.mask_window.title(f"生成{color}掩模")
        tk.Label(self.mask_window, text=f"生成{color}掩模").pack(pady=10)
        # 掩模生成规则：对应通道大于其它通道则为掩模
        img = self.image
        if len(img.shape) == 2:
            tk.messagebox.showinfo("提示", "掩模仅支持彩色图像！")
            self.mask_window.destroy()
            self.mask_window = None
            return
        if color == 'red':
            mask = (img[:,:,0] > img[:,:,1]) & (img[:,:,0] > img[:,:,2])
        elif color == 'green':
            mask = (img[:,:,1] > img[:,:,0]) & (img[:,:,1] > img[:,:,2])
        elif color == 'blue':
            mask = (img[:,:,2] > img[:,:,0]) & (img[:,:,2] > img[:,:,1])
        else:
            mask = np.zeros(img.shape[:2], dtype=bool)
        mask_img = (mask * 255).astype(np.uint8)
        self.mask_image = mask_img
        # 显示掩模预览
        self.mask_preview_canvas = tk.Canvas(self.mask_window, width=300, height=200)
        self.mask_preview_canvas.pack(pady=10)
        pil_mask = Image.fromarray(mask_img).resize((300,200))
        photo = ImageTk.PhotoImage(pil_mask)
        self.mask_preview_canvas.create_image(0, 0, anchor=tk.NW, image=photo)
        self.mask_preview_canvas.image = photo
        tk.Button(self.mask_window, text="保存掩模", command=self.save_mask_image).pack(pady=5)
        tk.Button(self.mask_window, text="应用掩模", command=self.show_mask_apply_dialog).pack(pady=5)
        def on_close():
            self.mask_window.destroy()
            self.mask_window = None
        self.mask_window.protocol("WM_DELETE_WINDOW", on_close)

    def save_mask_image(self):
        """保存当前掩模图像"""
        if self.mask_image is None:
            tk.messagebox.showinfo("提示", "没有可保存的掩模！")
            return
        save_path = filedialog.asksaveasfilename(defaultextension='.png', filetypes=[('PNG文件', '*.png')])
        if not save_path:
            return
        Image.fromarray(self.mask_image).save(save_path)
        tk.messagebox.showinfo("提示", f"掩模已保存到 {save_path}")

    def show_mask_apply_dialog(self):
        """弹出掩模应用窗口，支持四种运算"""
        if self.mask_image is None or self.image is None:
            tk.messagebox.showinfo("提示", "请先生成掩模并载入彩色图像！")
            return
        if self.mask_apply_window is not None:
            self.mask_apply_window.destroy()
        self.mask_apply_window = tk.Toplevel(self.master)
        self.mask_apply_window.title("掩模运算")
        tk.Label(self.mask_apply_window, text="选择掩模运算类型").pack(pady=10)
        
        # 创建运算按钮框架
        btn_frame = tk.Frame(self.mask_apply_window)
        btn_frame.pack(pady=10)
        
        # 添加四种运算按钮
        btn_and = tk.Button(btn_frame, text="与运算 (AND)", command=self.apply_mask_and, width=15)
        btn_and.grid(row=0, column=0, padx=5, pady=5)
        
        btn_or = tk.Button(btn_frame, text="或运算 (OR)", command=self.apply_mask_or, width=15)
        btn_or.grid(row=0, column=1, padx=5, pady=5)
        
        btn_xor = tk.Button(btn_frame, text="异或运算 (XOR)", command=self.apply_mask_xor, width=15)
        btn_xor.grid(row=1, column=0, padx=5, pady=5)
        
        btn_not = tk.Button(btn_frame, text="非运算 (NOT)", command=self.apply_mask_not, width=15)
        btn_not.grid(row=1, column=1, padx=5, pady=5)
        
        def on_close():
            self.mask_apply_window.destroy()
            self.mask_apply_window = None
        self.mask_apply_window.protocol("WM_DELETE_WINDOW", on_close)

    def apply_mask_and(self):
        """与运算：主图像与掩模做与运算"""
        if self.mask_image is None or self.image is None:
            tk.messagebox.showinfo("提示", "请先生成掩模并载入彩色图像！")
            return
        img = self.image
        mask = self.mask_image
        if len(img.shape) == 2:
            tk.messagebox.showinfo("提示", "主图像需为彩色图像！")
            return
        # 修正：支持3通道和4通道（带alpha），只对前三通道做掩模运算
        if img.shape[2] == 3:
            mask3 = np.stack([mask]*3, axis=2)
            result = (img & mask3).astype(np.uint8)
        elif img.shape[2] == 4:
            mask3 = np.stack([mask]*3, axis=2)
            rgb = (img[:,:,:3] & mask3).astype(np.uint8)
            alpha = img[:,:,3:4]
            result = np.concatenate([rgb, alpha], axis=2)
        else:
            tk.messagebox.showinfo("提示", "暂不支持该通道数的图像！")
            return
        self.mask_result_image = result
        self.show_mask_result_window(result, "与运算结果")

    def apply_mask_or(self):
        """或运算：主图像与掩模做或运算"""
        if self.mask_image is None or self.image is None:
            tk.messagebox.showinfo("提示", "请先生成掩模并载入彩色图像！")
            return
        img = self.image
        mask = self.mask_image
        if len(img.shape) == 2:
            tk.messagebox.showinfo("提示", "主图像需为彩色图像！")
            return
        if img.shape[2] == 3:
            mask3 = np.stack([mask]*3, axis=2)
            result = (img | mask3).astype(np.uint8)
        elif img.shape[2] == 4:
            mask3 = np.stack([mask]*3, axis=2)
            rgb = (img[:,:,:3] | mask3).astype(np.uint8)
            alpha = img[:,:,3:4]
            result = np.concatenate([rgb, alpha], axis=2)
        else:
            tk.messagebox.showinfo("提示", "暂不支持该通道数的图像！")
            return
        self.mask_result_image = result
        self.show_mask_result_window(result, "或运算结果")

    def apply_mask_xor(self):
        """异或运算：主图像与掩模做异或运算"""
        if self.mask_image is None or self.image is None:
            tk.messagebox.showinfo("提示", "请先生成掩模并载入彩色图像！")
            return
        img = self.image
        mask = self.mask_image
        if len(img.shape) == 2:
            tk.messagebox.showinfo("提示", "主图像需为彩色图像！")
            return
        if img.shape[2] == 3:
            mask3 = np.stack([mask]*3, axis=2)
            result = (img ^ mask3).astype(np.uint8)
        elif img.shape[2] == 4:
            mask3 = np.stack([mask]*3, axis=2)
            rgb = (img[:,:,:3] ^ mask3).astype(np.uint8)
            alpha = img[:,:,3:4]
            result = np.concatenate([rgb, alpha], axis=2)
        else:
            tk.messagebox.showinfo("提示", "暂不支持该通道数的图像！")
            return
        self.mask_result_image = result
        self.show_mask_result_window(result, "异或运算结果")

    def apply_mask_not(self):
        """非运算：主图像与掩模做非运算"""
        if self.mask_image is None or self.image is None:
            tk.messagebox.showinfo("提示", "请先生成掩模并载入彩色图像！")
            return
        img = self.image
        mask = self.mask_image
        if len(img.shape) == 2:
            tk.messagebox.showinfo("提示", "主图像需为彩色图像！")
            return
        if img.shape[2] == 3:
            mask3 = np.stack([mask]*3, axis=2)
            result = (img & (~mask3)).astype(np.uint8)
        elif img.shape[2] == 4:
            mask3 = np.stack([mask]*3, axis=2)
            rgb = (img[:,:,:3] & (~mask3)).astype(np.uint8)
            alpha = img[:,:,3:4]
            result = np.concatenate([rgb, alpha], axis=2)
        else:
            tk.messagebox.showinfo("提示", "暂不支持该通道数的图像！")
            return
        self.mask_result_image = result
        self.show_mask_result_window(result, "非运算结果")

    def show_mask_result_window(self, result_img, title):
        """显示掩模运算结果窗口，可保存结果"""
        win = tk.Toplevel(self.master)
        win.title(title)
        canvas = tk.Canvas(win, width=300, height=200)
        canvas.pack(pady=10)
        pil_img = Image.fromarray(result_img).resize((300,200))
        photo = ImageTk.PhotoImage(pil_img)
        canvas.create_image(0, 0, anchor=tk.NW, image=photo)
        canvas.image = photo
        def save_result():
            save_path = filedialog.asksaveasfilename(defaultextension='.png', filetypes=[('PNG文件', '*.png')])
            if not save_path:
                return
            Image.fromarray(result_img).save(save_path)
            tk.messagebox.showinfo("提示", f"结果已保存到 {save_path}")
        tk.Button(win, text="保存结果", command=save_result).pack(pady=5)
        def on_close():
            win.destroy()
        win.protocol("WM_DELETE_WINDOW", on_close)

    def invert_image(self):
        """反色（非运算）功能，对当前图像取反，兼容.png（含alpha通道）"""
        if self.image is None:
            tk.messagebox.showinfo("提示", "请先载入一张图像！")
            return
        img = self.image.copy()
        # 保证为uint8类型
        if img.dtype != np.uint8:
            img = img.astype(np.uint8)
        # 判断是否为4通道（带alpha）
        if len(img.shape) == 3 and img.shape[2] == 4:
            rgb = img[:, :, :3]
            alpha = img[:, :, 3:4]
            inverted_rgb = 255 - rgb
            inverted = np.concatenate([inverted_rgb, alpha], axis=2)
        else:
            inverted = 255 - img
        self.image = inverted.astype(np.uint8)
        self.original_image = self.image.copy()
        self.display_image()
        tk.messagebox.showinfo("提示", "已完成反色/非运算！")

# 创建主窗口和应用程序实例
root = tk.Tk()
app = ImageProcessor(root)
root.mainloop()